package com.uuah.server.config.spring.parsers.generic;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

import com.uuah.server.config.spring.parsers.assembly.BeanAssembler;
import com.uuah.server.config.spring.parsers.assembly.IBeanAssembler;
import com.uuah.server.config.spring.parsers.assembly.IBeanAssemblerFactory;
import com.uuah.server.config.spring.parsers.assembly.configuration.IPropertyConfiguration;
import com.uuah.server.config.spring.parsers.assembly.configuration.ISingleProperty;

/**
 * This extends {@link ParentDefinitionParser} so that the "name" attribute is
 * set locally, not on the parent.
 * 
 */
public class NameTransferDefinitionParser extends ParentDefinitionParser {

	private String name;
	private String componentAttributeValue;
	private String componentAttributeName;

	/**
	 * @param componentAttributeName
	 *            The attribute name (after processing, which will strip "-ref",
	 *            add plurals, etc) that identifies the service which will
	 *            receive the "name".
	 */
	public NameTransferDefinitionParser(String componentAttributeName) {
		this.componentAttributeName = componentAttributeName;
		setBeanAssemblerFactory(new LocalBeanAssemblerFactory());
	}

	// this is a bit of a hack - we transfer the name to the provider

	// reset for each use
	protected AbstractBeanDefinition parseInternal(Element element,
			ParserContext parserContext) {
		name = null;
		componentAttributeValue = null;
		AbstractBeanDefinition bd = super.parseInternal(element, parserContext);
		element.removeAttribute(ATTRIBUTE_NAME);
		return bd;
	}

	// only set name if not already given
	private void setName() {
		BeanDefinition beanDef = getParserContext().getRegistry()
				.getBeanDefinition(componentAttributeValue);
		MutablePropertyValues propertyValues = beanDef.getPropertyValues();
		if (!propertyValues.contains(ATTRIBUTE_NAME)) {
			logger.debug("Setting " + ATTRIBUTE_NAME + " on "
					+ componentAttributeValue + " to " + name);
			propertyValues.addPropertyValue(ATTRIBUTE_NAME, name);
		} else {
			logger.debug("Not setting " + ATTRIBUTE_NAME + " on "
					+ componentAttributeValue + " as already "
					+ propertyValues.getPropertyValue(ATTRIBUTE_NAME));
		}
	}

	private class LocalBeanAssembler extends BeanAssembler {

		public LocalBeanAssembler(IPropertyConfiguration beanConfig,
				BeanDefinitionBuilder bean, IPropertyConfiguration targetConfig,
				BeanDefinition target) {
			super(beanConfig, bean, targetConfig, target);
		}

		protected void addPropertyWithReference(
				MutablePropertyValues properties, ISingleProperty config,
				String name, Object value) {
			// intercept setting of name
			if (ATTRIBUTE_NAME.equals(name) && value instanceof String) {
				NameTransferDefinitionParser.this.name = (String) value;
				// name is set after service
				if (null != componentAttributeValue) {
					setName();
				}
			} else {
				super.addPropertyWithReference(properties, config, name, value);

				// intercept setting of service
				if (componentAttributeName.equals(name)
						&& value instanceof String) {
					componentAttributeValue = (String) value;
					// name was set before service
					if (null != NameTransferDefinitionParser.this.name) {
						setName();
					}
				}
			}
		}
	}

	private class LocalBeanAssemblerFactory implements IBeanAssemblerFactory {

		public IBeanAssembler newBeanAssembler(IPropertyConfiguration beanConfig,
				BeanDefinitionBuilder bean, IPropertyConfiguration targetConfig,
				BeanDefinition target) {
			return new LocalBeanAssembler(beanConfig, bean, targetConfig,
					target);
		}

	}

}